如果說陣列是「一排資料」,物件就是「一群有名字的資料」,用大括號 {} 宣告,每筆資料都是 key: value 的配對:
const hero = { name: "勇者", hp: 100, atk: 20, def: 5 };
const enemy = { name: "小怪", hp: 60, atk: 15, def: 3 };
比起用一堆分散的變數(heroName、heroHp、heroAtk……),物件把「同一個角色」的所有資料打包成一包,管理起來更直覺。用點記法 . 存取:
console.log(hero.name, "的 HP:", hero.hp); // 勇者 的 HP: 100
console.log(enemy.name, "的 HP:", enemy.hp); // 小怪 的 HP: 60
補充一下:JS 的物件概念上跟 Python 的字典(dict)一樣,都是 key-value 結構,只是 Python 習慣用中括號存取(hero["name"]),JS 習慣用點記法(hero.name)。
不過 JS 其實也支援中括號寫法,hero["name"] 跟 hero.name 效果完全一樣,只是點記法比較簡潔、業界也更常用這種寫法。中括號寫法還有一個點記法做不到的用途:當屬性名稱是存在變數裡的字串時,只能用中括號,例如 hero[someKey]——這也是今天迴圈清空練習裡 hero[keys[i]] 用中括號而不是點記法的原因。
還記得 Day 2 埋的伏筆嗎?「const 鎖住的是變數指向誰,不是內容完全不能動」——今天親自驗證了這件事。
先修改物件內部的屬性:
hero.hp = hero.hp - 30;
console.log("勇者受傷後 HP:", hero.hp); // 70
這樣是合法的,const 沒有阻止我修改 hero 裡面的內容,只是不能把 hero 整個換成別的物件。
物件驗證完之後,我很好奇陣列是不是也是同一套規則——畢竟陣列本質上也是一種物件(還記得 Day 5 挖到的 [[Prototype]] 嗎?)。
const arr = ['你', '我', '他'];
arr[0] = '0';
arr[1] = '1';
arr[2] = '2';
console.log(arr);
結果證實規則完全一致:一格一格改是合法的,const 沒有抗議,因為 arr 從頭到尾都指向同一個陣列,只是內容變了。如果改成 arr = ['0','1','2'] 這種整個重新賦值的寫法,就會跟物件一樣噴出 TypeError。
驗證這個的時候,我發現一件很奇怪的事:我在改值之前就先 console.log(arr) 一次,理論上應該印出 ['你','我','他']。但當我點開這行紀錄展開來看細節時,裡面顯示的卻是 0: "0", 1: "1", 2: "2"——跟我印的當下明明不一樣!
查了才知道:console.log() 印物件或陣列時,存的不是「當下那一刻的快照」,而是一個「活的參照」。摺疊起來的預覽文字(像 (3) ['你', '我', '他'])確實是 log 當下拍的快照,但點開展開查看內部細節時,Console 會重新讀取這個陣列「現在」的真實狀態——如果程式碼在那之後又把陣列改了,點開看到的就會是改過的新內容,不是當初 log 那一刻的內容。

這不是 JavaScript 語言本身的行為,是瀏覽器開發者工具顯示機制的特性,但是個很多工程師都踩過的坑。如果之後想在 Console 裡真正凍結某個時間點的快照,我也順手測了兩種解法,確認都真的有效:
console.log([...arr]); // 複製一份新陣列,之後原陣列再怎麼改都不影響這份紀錄
console.log(JSON.stringify(arr)); // 轉成字串,鎖住當下的值

延伸這個觀念,我測了新增和刪除:
// 新增一個屬性
hero.skill = "火球術";
console.log("新增後:", hero);
// 刪除一個屬性
delete hero.atk;
console.log("刪除 atk 後:", hero);
結果:
新增後: {name: '勇者', hp: 70, atk: 20, def: 5, skill: '火球術'}
刪除 atk 後: {name: '勇者', hp: 70, def: 5, skill: '火球術'}
新增用直接賦值就能加(hero.skill = ...),刪除要用 delete 這個運算子——兩者都是在動「內容」,不是換掉整個變數,所以對 const 來說都合法。

JS 沒有像陣列那樣提供「一鍵清空物件」的功能,想清空只能一個一個刪除鍵。我用 Object.keys() 把所有鍵名抓出來,搭配迴圈逐一刪除,每刪一次就印出剩下多少:
const keys = Object.keys(hero);
for (let i = 0; i < keys.length; i++) {
delete hero[keys[i]];
console.log(`刪除 ${keys[i]} 後,剩下:`, hero);
}
console.log("最終結果:", hero);
(這裡的 for 迴圈語法明天才會正式教,先照著打,有個初步印象就好。)
結果完整記錄了物件一步步被清空的過程:
刪除 name 後,剩下: {hp: 70, def: 5, skill: '火球術'}
刪除 hp 後,剩下: {def: 5, skill: '火球術'}
刪除 def 後,剩下: {skill: '火球術'}
刪除 skill 後,剩下: {}
最終結果: {}
從頭到尾,hero 都是同一個變數、同一個物件,只是裡面的內容越來越少——這正好證明「清空內容」跟「重新賦值」是兩種完全不同的操作。
物件清空之後,我試著把 hero 整個換成一個全新的物件:
hero = { name: "復活勇者" };
結果:
Uncaught TypeError: Assignment to constant variable.
Day 2 的伏筆到這裡正式驗證完畢:不管物件裡面的內容怎麼新增、刪除、清空,只要不是「把整個變數重新指向另一個物件」,const 都不會抗議;但只要做了「整個換掉」這個動作,馬上噴錯。
今天搞懂的事:
. 存取屬性,概念上等同 Python 的字典const 只鎖「變數指向誰」,物件內部的屬性可以自由新增、修改、刪除delete,清空要靠迴圈逐一刪除,沒有一鍵完成的方法const,這點在今天的測試裡得到完整驗證const 規則;console.log 印物件/陣列是活的參照不是快照,想凍結當下的值要用 [...arr] 或 JSON.stringify()
明天繼續學迴圈——今天先偷用了 for,明天正式把它學好。